/* Per-checkout call state/caching */
typedef struct {
- GString *selabel_path_buf;
+ GString *path_buf; /* buffer for real path if filtering enabled */
+ GString *selabel_path_buf; /* buffer for selinux path if labeling enabled; this may be
+ the same buffer as path_buf */
} CheckoutState;
static void
checkout_state_clear (CheckoutState *state)
{
- if (state->selabel_path_buf)
+ if (state->path_buf)
+ g_string_free (state->path_buf, TRUE);
+ if (state->selabel_path_buf && (state->selabel_path_buf != state->path_buf))
g_string_free (state->selabel_path_buf, TRUE);
}
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(CheckoutState, checkout_state_clear)
static gboolean
checkout_one_file_at (OstreeRepo *repo,
- OstreeRepoCheckoutAtOptions *options,
+ OstreeRepoCheckoutAtOptions *options,
CheckoutState *state,
const char *checksum,
int destination_dfd,
gboolean is_bare_user_symlink = FALSE;
char loose_path_buf[_OSTREE_LOOSE_PATH_MAX];
+
/* FIXME - avoid the GFileInfo here */
g_autoptr(GFileInfo) source_info = NULL;
if (!ostree_repo_load_file (repo, checksum, NULL, &source_info, NULL,
cancellable, error))
return FALSE;
+ if (options->filter)
+ {
+ /* use struct stat for when we can get rid of GFileInfo; though for now, we end up
+ * packing and unpacking in the non-archive case; blehh */
+ struct stat stbuf = {0,};
+ _ostree_gfileinfo_to_stbuf (source_info, &stbuf);
+ if (options->filter (repo, state->path_buf->str, &stbuf, options->filter_user_data) ==
+ OSTREE_REPO_CHECKOUT_FILTER_SKIP)
+ return TRUE; /* Note early return */
+ }
+
const gboolean is_symlink = (g_file_info_get_file_type (source_info) == G_FILE_TYPE_SYMBOLIC_LINK);
const gboolean is_whiteout = (!is_symlink && options->process_whiteouts &&
g_str_has_prefix (destination_name, WHITEOUT_PREFIX));
return TRUE;
}
+static inline void
+push_path_element_once (GString *buf,
+ const char *name,
+ gboolean is_dir)
+{
+ g_string_append (buf, name);
+ if (is_dir)
+ g_string_append_c (buf, '/');
+}
+
+static inline void
+push_path_element (OstreeRepoCheckoutAtOptions *options,
+ CheckoutState *state,
+ const char *name,
+ gboolean is_dir)
+{
+ if (state->path_buf)
+ push_path_element_once (state->path_buf, name, is_dir);
+ if (state->selabel_path_buf && (state->selabel_path_buf != state->path_buf))
+ push_path_element_once (state->selabel_path_buf, name, is_dir);
+}
+
+static inline void
+pop_path_element (OstreeRepoCheckoutAtOptions *options,
+ CheckoutState *state,
+ const char *name,
+ gboolean is_dir)
+{
+ const size_t n = strlen (name) + (is_dir ? 1 : 0);
+ if (state->path_buf)
+ g_string_truncate (state->path_buf, state->path_buf->len - n);
+ if (state->selabel_path_buf && (state->selabel_path_buf != state->path_buf))
+ g_string_truncate (state->selabel_path_buf, state->selabel_path_buf->len - n);
+}
+
/*
* checkout_tree_at:
* @self: Repo
gid = GUINT32_FROM_BE (gid);
mode = GUINT32_FROM_BE (mode);
+ if (options->filter)
+ {
+ struct stat stbuf = { 0, };
+ stbuf.st_mode = mode;
+ stbuf.st_uid = uid;
+ stbuf.st_gid = gid;
+ if (options->filter (self, state->path_buf->str, &stbuf, options->filter_user_data)
+ == OSTREE_REPO_CHECKOUT_FILTER_SKIP)
+ return TRUE; /* Note early return */
+ }
+
/* First, make the directory. Push a new scope in case we end up using
* setfscreatecon().
*/
return FALSE;
}
- GString *selabel_path_buf = state->selabel_path_buf;
/* Process files in this subdir */
{ g_autoptr(GVariant) dir_file_contents = g_variant_get_child_value (dirtree, 0);
GVariantIter viter;
g_autoptr(GVariant) contents_csum_v = NULL;
while (g_variant_iter_loop (&viter, "(&s@ay)", &fname, &contents_csum_v))
{
- const size_t origlen = selabel_path_buf ? selabel_path_buf->len : 0;
- if (selabel_path_buf)
- g_string_append (selabel_path_buf, fname);
+ push_path_element (options, state, fname, FALSE);
char tmp_checksum[OSTREE_SHA256_STRING_LEN+1];
_ostree_checksum_inplace_from_bytes_v (contents_csum_v, tmp_checksum);
cancellable, error))
return FALSE;
- if (selabel_path_buf)
- g_string_truncate (selabel_path_buf, origlen);
+ pop_path_element (options, state, fname, FALSE);
}
contents_csum_v = NULL; /* iter_loop freed it */
}
if (!ot_util_filename_validate (dname, error))
return FALSE;
- const size_t origlen = selabel_path_buf ? selabel_path_buf->len : 0;
- if (selabel_path_buf)
- {
- g_string_append (selabel_path_buf, dname);
- g_string_append_c (selabel_path_buf, '/');
- }
+ push_path_element (options, state, dname, TRUE);
char subdirtree_checksum[OSTREE_SHA256_STRING_LEN+1];
_ostree_checksum_inplace_from_bytes_v (subdirtree_csum_v, subdirtree_checksum);
cancellable, error))
return FALSE;
- if (selabel_path_buf)
- g_string_truncate (selabel_path_buf, origlen);
+ pop_path_element (options, state, dname, TRUE);
}
}
GError **error)
{
g_auto(CheckoutState) state = { 0, };
- // If SELinux labeling is enabled, we need to keep track of the full path string
+
+ if (options->filter)
+ state.path_buf = g_string_new ("/");
+
+ /* If SELinux labeling is enabled, we need to keep track of the full path string */
if (options->sepolicy)
{
- GString *buf = g_string_new (options->sepolicy_prefix ?: options->subpath);
- g_assert_cmpint (buf->len, >, 0);
- // Ensure it ends with /
- if (buf->str[buf->len-1] != '/')
- g_string_append_c (buf, '/');
- state.selabel_path_buf = buf;
-
/* Otherwise it'd just be corrupting things, and there's no use case */
g_assert (options->force_copy);
+
+ const char *prefix = options->sepolicy_prefix ?: options->subpath;
+ if (g_str_equal (prefix, "/") && state.path_buf)
+ {
+ /* just use the same scratchpad if we can */
+ state.selabel_path_buf = state.path_buf;
+ }
+ else
+ {
+ GString *buf = g_string_new (prefix);
+ g_assert_cmpint (buf->len, >, 0);
+ /* Ensure it ends with / */
+ if (buf->str[buf->len-1] != '/')
+ g_string_append_c (buf, '/');
+ state.selabel_path_buf = buf;
+ }
}
/* Special case handling for subpath of a non-directory */
*/
int destination_dfd = destination_parent_fd;
glnx_autofd int destination_dfd_owned = -1;
- if (strcmp (destination_name, ".") != 0)
+ if (!g_str_equal (destination_name, "."))
{
if (mkdirat (destination_parent_fd, destination_name, 0700) < 0
&& errno != EEXIST)
return FALSE;
destination_dfd = destination_dfd_owned;
}
+ /* let's just ignore filter here; I can't think of a useful case for filtering when
+ * only checking out one path */
+ options->filter = NULL;
return checkout_one_file_at (self, options, &state,
ostree_repo_file_get_checksum (source),
destination_dfd,
static gboolean opt_require_hardlinks;
static gboolean opt_force_copy;
static gboolean opt_bareuseronly_dirs;
+static char *opt_skiplist_file;
static char *opt_selinux_policy;
static char *opt_selinux_prefix;
{ "require-hardlinks", 'H', 0, G_OPTION_ARG_NONE, &opt_require_hardlinks, "Do not fall back to full copies if hardlinking fails", NULL },
{ "force-copy", 'C', 0, G_OPTION_ARG_NONE, &opt_force_copy, "Never hardlink (but may reflink if available)", NULL },
{ "bareuseronly-dirs", 'M', 0, G_OPTION_ARG_NONE, &opt_bareuseronly_dirs, "Suppress mode bits outside of 0775 for directories (suid, world writable, etc.)", NULL },
+ { "skip-list", 0, 0, G_OPTION_ARG_FILENAME, &opt_skiplist_file, "File containing list of files to skip", "PATH" },
{ "selinux-policy", 0, 0, G_OPTION_ARG_FILENAME, &opt_selinux_policy, "Set SELinux labels based on policy in root filesystem PATH (may be /); implies --force-copy", "PATH" },
{ "selinux-prefix", 0, 0, G_OPTION_ARG_STRING, &opt_selinux_prefix, "When setting SELinux labels, prefix all paths by PREFIX", "PREFIX" },
{ NULL }
};
+static gboolean
+handle_skiplist_line (const char *line,
+ void *data,
+ GError **error)
+{
+ GHashTable *files = data;
+ g_hash_table_add (files, g_strdup (line));
+ return TRUE;
+}
+
+static OstreeRepoCheckoutFilterResult
+checkout_filter (OstreeRepo *self,
+ const char *path,
+ struct stat *st_buf,
+ gpointer user_data)
+{
+ GHashTable *skiplist = user_data;
+ if (g_hash_table_contains (skiplist, path))
+ return OSTREE_REPO_CHECKOUT_FILTER_SKIP;
+ return OSTREE_REPO_CHECKOUT_FILTER_ALLOW;
+}
+
static gboolean
process_one_checkout (OstreeRepo *repo,
const char *resolved_commit,
*/
if (opt_disable_cache || opt_whiteouts || opt_require_hardlinks ||
opt_union_add || opt_force_copy || opt_bareuseronly_dirs || opt_union_identical ||
- opt_selinux_policy || opt_selinux_prefix)
+ opt_skiplist_file || opt_selinux_policy || opt_selinux_prefix)
{
OstreeRepoCheckoutAtOptions options = { 0, };
options.sepolicy_prefix = opt_selinux_prefix;
}
+ g_autoptr(GHashTable) skip_list =
+ g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ if (opt_skiplist_file)
+ {
+ if (!ot_parse_file_by_line (opt_skiplist_file, handle_skiplist_line, skip_list,
+ cancellable, error))
+ goto out;
+ options.filter = checkout_filter;
+ options.filter_user_data = skip_list;
+ }
+
options.no_copy_fallback = opt_require_hardlinks;
options.force_copy = opt_force_copy;
options.bareuseronly_dirs = opt_bareuseronly_dirs;